/*
 * Bytecode Analysis Framework
 * Copyright (C) 2003,2004 University of Maryland
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs.ba;

import java.util.Collection;
import java.util.HashSet;

import edu.umd.cs.findbugs.ba.vna.ValueNumber;
import edu.umd.cs.findbugs.ba.vna.ValueNumberFactory;
import edu.umd.cs.findbugs.ba.vna.ValueNumberFrame;

/**
 * Lock counts for values (as produced by ValueNumberAnalysis).
 * A LockSet tells us the lock counts for all values in a method,
 * insofar as we can accurately determine them.
 *
 * @author David Hovemeyer
 * @see edu.umd.cs.findbugs.ba.vna.ValueNumberAnalysis
 */
public final class LockSet {
	/**
	 * An uninitialized lock value.
	 */
	public static final int TOP = -1;

	/**
	 * An invalid lock count resulting from the meet of two
	 * different (inconsistent) lock counts.
	 */
	public static final int BOTTOM = -2;

	private static final int INVALID = -1;
	private static final int DEFAULT_CAPACITY = 8;

	/**
	 * Lock counts are stored in an array.
	 * Even indices <i>i</i> are value numbers of lock objects.
	 * Odd indices <i>i+1</i> are lock counts.
	 * This representation is fairly compact in memory.
	 */
	private int[] array;

	/**
	 * The lock count value to return for nonexistent lock entries.
	 */
	private int defaultLockCount;

	/**
	 * Constructor.
	 * Creates an empty lock set which returns TOP for
	 * nonexistent lock entries.
	 */
	public LockSet() {
		this.array = new int[DEFAULT_CAPACITY];
		this.defaultLockCount = TOP;
		clear();
	}

	/**
	 * Get the lock count for given lock object.
	 *
	 * @param valueNumber value number of the lock object
	 * @return the lock count for the lock object
	 */
	public int getLockCount(int valueNumber) {
		int index = findIndex(valueNumber);
		if (index < 0)
			return defaultLockCount;
		else
			return array[index + 1];
	}

	public boolean isTop() {
		return defaultLockCount == TOP;
	}
	/**
	 * Set the lock count for a lock object.
	 *
	 * @param valueNumber value number of the lock object
	 * @param lockCount   the lock count for the lock
	 */
	public void setLockCount(int valueNumber, int lockCount) {
		int index = findIndex(valueNumber);
		if (index < 0) {
			addEntry(index, valueNumber, lockCount);
		} else {
			array[index + 1] = lockCount;
		}
	}

	/**
	 * Set the default lock count to return for nonexistent lock entries.
	 *
	 * @param defaultLockCount the default lock count value
	 */
	public void setDefaultLockCount(int defaultLockCount) {
		this.defaultLockCount = defaultLockCount;
	}

	/**
	 * Get the number of distinct lock values with positive lock counts.
	 */
	public int getNumLockedObjects() {
		int result = 0;
		for (int i = 0; i < array.length; i += 2) {
			if (array[i] == INVALID)
				break;
			if (array[i + 1] > 0)
				++result;
		}
		return result;
	}

	/**
	 * Make this LockSet the same as the given one.
	 *
	 * @param other the LockSet to copy
	 */
	public void copyFrom(LockSet other) {
		if (other.array.length != array.length) {
			array = new int[other.array.length];
		}
		System.arraycopy(other.array, 0, array, 0, array.length);
		this.defaultLockCount = other.defaultLockCount;
	}

	/**
	 * Clear all entries out of this LockSet.
	 */
	public void clear() {
		for (int i = 0; i < array.length; i += 2) {
			array[i] = INVALID;
		}
	}

	/**
	 * Meet this LockSet with another LockSet,
	 * storing the result in this object.
	 *
	 * @param other the other LockSet
	 */
	public void meetWith(LockSet other) {
		for (int i = 0; i < array.length; i += 2) {
			int valueNumber = array[i];
			if (valueNumber < 0)
				break;

			int mine = array[i + 1];
			int his = other.getLockCount(valueNumber);
			array[i + 1] = mergeValues(mine, his);
		}

		for (int i = 0; i < other.array.length; i += 2) {
			int valueNumber = other.array[i];
			if (valueNumber < 0)
				break;

			int mine = getLockCount(valueNumber);
			int his = other.array[i + 1];
			setLockCount(valueNumber, mergeValues(mine, his));
		}

		setDefaultLockCount(0);
	}

	/**
	 * Return whether or not this LockSet is the same as the one given.
	 *
	 * @param other the other LockSet
	 */
	public boolean sameAs(LockSet other) {
		return this.identicalSubset(other) && other.identicalSubset(this);
	}

	/**
	 * Determine whether or not this lock set contains any
	 * locked values which are method return values.
	 *
	 * @param factory the ValueNumberFactory that produced the lock values
	 */
	public boolean containsReturnValue(ValueNumberFactory factory) {
		for (int i = 0; i < array.length; i += 2) {
			int valueNumber = array[i];
			if (valueNumber < 0)
				break;
			int lockCount = array[i + 1];
			if (lockCount > 0 && factory.forNumber(valueNumber).hasFlag(ValueNumber.RETURN_VALUE))
				return true;
		}
		return false;
	}

	/**
	 * Destructively intersect this lock set with another.
	 * Note that this is <em>not</em> a dataflow merge:
	 * we are interested in finding out which locks are held
	 * in both sets, not in the exact lock counts.
	 *
	 * @param other the other LockSet
	 */
	public void intersectWith(LockSet other) {
		for (int i = 0; i < array.length; i += 2) {
			int valueNumber = array[i];
			if (valueNumber < 0)
				break;
			int myLockCount = array[i + 1];
			if (myLockCount <= 0)
				continue;
			int otherLockCount = other.getLockCount(valueNumber);
			if (otherLockCount <= 0) {
				/* This set holds the lock, but the other one doesn't. */
				array[i + 1] = 0;
			}
		}
	}

	/**
	 * Return whether or not this lock set is empty,
	 * meaning that no locks have a positive lock count.
	 *
	 * @return true if no locks are held, false if at least
	 *         one lock is held
	 */
	public boolean isEmpty() {
		for (int i = 0; i < array.length; i += 2) {
			int valueNumber = array[i];
			if (valueNumber < 0)
				return true;
			int myLockCount = array[i + 1];
			if (myLockCount > 0)
				return false;
		}
		return true;
	}

	private boolean identicalSubset(LockSet other) {
		for (int i = 0; i < array.length; i += 2) {
			int valueNumber = array[i];
			if (valueNumber < 0)
				break;
			int mine = array[i + 1];
			int his = other.getLockCount(valueNumber);
			if (mine != his)
				return false;
			//System.out.println("For value " + valueNumber + ", " + mine + "==" + his);
		}
		return true;
	}

	private static int mergeValues(int a, int b) {
		if (a == TOP)
			return b;
		else if (b == TOP)
			return a;
		else if (a == BOTTOM || b == BOTTOM)
			return BOTTOM;
		else if (a == b)
			return a;
		else
			return BOTTOM;
	}

	private int findIndex(int valueNumber) {
		for (int i = 0; i < array.length; i += 2) {
			int value = array[i];
			if (value < 0)
				return -(i + 1); // didn't find requested valueNumber - return first available slot
			else if (value == valueNumber)
				return i; // found requested valueNumber
		}
		return -(array.length + 1); // didn't find requested valueNumber, and array is full
	}

	private void addEntry(int negatedIndex, int valueNumber, int lockCount) {
		int index = -(negatedIndex + 1);
		int origCapacity = array.length;
		if (index == origCapacity) {
			// Grow the array.

			// Allocate twice the space of the old array
			int[] data = new int[origCapacity * 2];

			// Copy existing data
			System.arraycopy(array, 0, data, 0, origCapacity);

			// Clear entries in the new part of the array
			// that won't be used to store the entry that's
			// being added
			for (int i = index + 2; i < data.length; i += 2) {
				data[i] = INVALID;
			}

			// Now we're ready to use the new array
			array = data;
		}

		array[index] = valueNumber;
		array[index + 1] = lockCount;
	}

	@Override
	public String toString() {
		StringBuilder buf = new StringBuilder();
		buf.append('[');
		boolean first = true;
		if (defaultLockCount == 0) {
			buf.append("default=0");
			first = false;
		}
		for (int i = 0; i < array.length; i += 2) {
			int valueNumber = array[i];
			if (valueNumber < 0)
				continue;
			int lockCount = array[i + 1];
			if (lockCount == 0)
				continue;
			if (first)
				first = false;
			else
				buf.append(',');
			buf.append(valueNumber);
			buf.append('=');
			if (lockCount == TOP)
				buf.append("TOP");
			else if (lockCount == BOTTOM)
				buf.append("BOTTOM");
			else
				buf.append(lockCount);
		}
		buf.append(']');
		return buf.toString();
	}

	/**
	 * @param frame
	 * @return a set of the locked value numbers
	 */
	public Collection<ValueNumber> getLockedValueNumbers(ValueNumberFrame frame) {
		if (frame == null) throw new IllegalArgumentException("Null Frame");
		HashSet<ValueNumber> result = new HashSet<ValueNumber>();
		for(ValueNumber v : frame.allSlots())
			if (v != null && getLockCount(v.getNumber()) > 0)
				result.add(v);
		return result;
	}


/*
	public static void main(String[] argv) {
		LockSet l = new LockSet();
		l.setLockCount(0, 1);
		System.out.println(l);
		l.setLockCount(0, 2);
		System.out.println(l);
		l.setLockCount(15, 1);
		System.out.println(l);
		LockSet ll = new LockSet();
		ll.setLockCount(0, 1);
		ll.setLockCount(15, 1);
		ll.setLockCount(69, 3);
		LockSet tmp = new LockSet();
		tmp.copyFrom(ll);
		ll.meetWith(l);
		System.out.println(l + " merge with " + tmp + " ==> " + ll);

		LockSet dup = new LockSet();
		dup.copyFrom(ll);
		System.out.println(ll + " == " + dup + " ==> " + ll.sameAs(dup));
		System.out.println(ll + " == " + l + " ==> " + ll.sameAs(l));
	}
*/
}

// vim:ts=4
